動手建立專案前,我們先追蹤一行命令從按下 Enter 到程式被啟動前的完整流程,釐清終端機、Shell 與作業系統各自做了什麼準備,才把控制權交給 CLI 程式。
當我們在終端機輸入一行命令並按下 Enter:
$ git commit -m "fix bug"
這段「進入程式前」的準備工作由三個系統角色分工完成:

本系列的命令以 macOS、Linux 上的 bash 或 zsh 語法為主。Windows 讀者可以在 WSL 中使用相同命令。
按下 Enter 後,終端機會把整行字串傳給 Shell。Shell 的第一項工作是純文字語法解析:辨識要執行的命令名稱,並處理引號、跳脫字元、變數與萬用字元,拆解出要傳給程式的參數清單。
在 git commit -m "fix bug" 這個例子中,Shell 的解析結果如下:

"fix bug" 被雙引號包住,因此 Shell 會把它拆成單一參數 fix bug,而不是按空白切成 fix 與 bug 兩個參數。引號是寫給 Shell 看的語法,解析完後不會成為參數內容本身。
此時 Shell 只知道 git 是命令名稱,後面跟著三個文字參數;Shell 完全不知道 commit 代表建立提交,也不知道 -m 代表指定提交訊息。
確定要執行的命令名稱是 git 後,Shell 需要找到這個執行檔在磁碟上的實際位置。如果輸入的是完整路徑(如 /usr/bin/git)或相對路徑(如 ./my-cli),Shell 會直接開啟該路徑;如果只輸入檔名 git,Shell 就會按照 PATH 環境變數列出的目錄依序搜尋。
PATH 是一組以冒號分隔的目錄清單:
$ printenv PATH
/usr/local/bin:/usr/bin:/bin
當我們輸入 git 時,Shell 會由左至右依序檢查各目錄中是否存在名為 git 的可執行檔:
/usr/local/bin/git
/usr/bin/git
/bin/git
只要在其中一個目錄(例如 /usr/bin/git)找到檔案,搜尋就會立刻停止。如果搜尋完所有目錄都找不到,Shell 就會顯示 zsh: command not found: git。
Shell 找到 /usr/bin/git 後,會向作業系統發出系統呼叫(System Call),請求建立一個全新的 Child Process 來執行它。
作業系統在啟動 Process 前,會為這個 Process 配置好初始執行環境,主要包含 4 種核心資源:
["git", "commit", "-m", "fix bug"]。PATH、USER 等由 Shell 繼承給 Child Process 的變數。配置好環境與資源後,作業系統將 Process 載入記憶體,並把控制權正式移交給 git 程式。
當 git 程式被啟動後,它會讀取 argv 中的參數、執行業務邏輯,並透過 stdout/stderr 印出結果。
程式完成工作後,會向作業系統回傳一個整數狀態碼,稱為退出碼(Exit Code):
0:代表程式順利執行成功。0(例如 1 或 128):代表執行失敗。$ git commit -m "fix bug"
[main 7a3f91b] fix bug
1 file changed, 2 insertions(+)
$ echo $?
0
作業系統收到 Exit Code 後清理 Process 資源,並通知等待中的 Shell。Shell 取得 Exit Code 知道程式已執行完成,隨即重新印出提示符號 $,等待使用者輸入下一個命令。
這就是一行命令在進入 CLI 程式前,終端機、Shell 與作業系統所完成的準備與轉手流程。下一篇我們將站在 CLI 開發者的角度,看程式接手後內部如何拆解與設計 Command、Arg 與 Flag。